package ro.uaic.info.fmse.context.transformers.generic;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import ro.uaic.info.fmse.context.transformers.enums.Multiplicity;
import ro.uaic.info.fmse.context.transformers.items.ConfigurationNode;
import ro.uaic.info.fmse.context.transformers.items.MyCloneable;

public class Tree<T extends MyCloneable> {
	private Node<T> root;
	private Set<Node<T>> nodes;
	private Set<Edge<T>> edges;
	
	public Tree(Node<T> root) {
		// set the root node
		this.root = root;
		// now, collect all nodes and edges
		NodeVisitor<T> visitor = new NodeVisitor<T>();
		dfs(visitor);
		nodes = visitor.getNodes();
		edges = visitor.getEdges();
	}
	
	public List<Node<T>> getNodesByLabel(String label) {
		return root.getNodesByLabel(label);
	}
	
	
	public Node<T> getNodeById(int ID) {
		return root.getNodeById(ID);
	}
	
	public int getDepth(int ID)
	{
		return root.getDepth(ID);
	}
	
	@SuppressWarnings("unchecked")
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return new Tree<T>((Node<T>) root.clone());
	}
	
	public Object getClone() throws CloneNotSupportedException
	{
		return this.clone();
	}

	public String toXML() {
		return root.toString();
	}
	
	@Override
	public String toString() {
		String out = root.toString() + "\n";
/*		for (Node<T> node : nodes)
			out += node.label + "; ";
		out += "\n";
		out += edges + "\n"; */
		return out;
	}
	
	
	/**
	 * This method add automatically a node as a child of another node. Trees
	 * should be constructed using only this method.
	 * 
	 * @param child
	 * @param parent
	 */
	public void appendNode(Node<T> child, Node<T> parent) {
		// silently ignore this situation
		if (parent == null || child == null)
			return;
		appendNode(child, parent.ID);
	}
	
	
	@SuppressWarnings("unused")
	private Multiplicity getInheritedMultiplicity(ConfigurationNode parent,
			ConfigurationNode child) {
		if (parent == null)
			return child.multiplicity;
		Multiplicity m = child.multiplicity;
		Multiplicity pm = parent.multiplicity;
		if (m == Multiplicity.ANY)
			return m;
		if (m == Multiplicity.NONE)
			return pm;
		if (m == Multiplicity.MAYBE) {
			if (pm == Multiplicity.NONE)
				return Multiplicity.MAYBE;
		} else
			return pm;
		if (m == Multiplicity.SOME)
			if (pm == Multiplicity.NONE || pm == Multiplicity.SOME)
				return Multiplicity.SOME;
			else
				return Multiplicity.ANY;
		return m;
	}
	
	
	/**
	 * This method add automatically a node as a child of another node. Trees
	 * should be constructed using only this method.
	 * 
	 * @param child
	 * @param parent
	 */
	public void appendNode(Node<T> child, int parentID) {
		// silently ignore this situation
		if (child == null)
			return;
		Node<T> realparent = getNodeById(parentID);
		if (realparent != null) {
			if (realparent.children == null)
				realparent.children = new ArrayList<Node<T>>();
			realparent.children.add(child);
			child.parent = realparent;
/*			// update the inherited multiplicity if any - This shoudn't be here!!!
			if (child.property instanceof ConfigurationNode) {
				ConfigurationNode config = (ConfigurationNode) child.property;
				config.inherited = getInheritedMultiplicity(
						(ConfigurationNode) realparent.property, config);
			} */
			nodes.add(child);
			edges.add(new Edge<T>(realparent, child));
		}
		
//		System.out.println("OUT:" + this + "\n\n\n");
		// now, collect all nodes and edges
		NodeVisitor<T> visitor = new NodeVisitor<T>();
		dfs(visitor);
		nodes = visitor.getNodes();
		edges = visitor.getEdges();
	}
	
	
	public Node<T> getRoot() {
		return root;
	}
	
	public Set<Node<T>> getNodes() {
		return nodes;
	}
	
	public Set<Edge<T>> getEdges() {
		return edges;
	}
	
	public void dfs(NodeVisitor<T> visitor) {
		dfs(visitor, this.root);
	}
	
	public void dfs(NodeVisitor<T> visitor, Node<T> node){
		if (node != null)
			node.accept(visitor);
		if (node.children != null)
			for(Node<T> n : node.children)
				dfs(visitor, n);
	}
}